package no.uib.hplims;

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.vaadin.appfoundation.authentication.SessionHandler;
import org.vaadin.appfoundation.persistence.facade.FacadeFactory;
import com.compomics.util.io.MascotEnzymeReader;
import com.compomics.util.protein.Enzyme;
import com.compomics.util.protein.Protein;
import com.vaadin.data.util.BeanItemContainer;
import com.vaadin.ui.Component;
import no.uib.hplims.jobs.BlastTool;
import no.uib.hplims.jobs.UniProtQueryTool;
import no.uib.hplims.models.Experiment;
import no.uib.hplims.models.MyOrder;
import no.uib.hplims.models.Peptide;
import no.uib.hplims.models.Rule;
import no.uib.hplims.models.RuleFactory;
import no.uib.hplims.models.User;
import no.uib.hplims.views.AbstractParentTab;
import no.uib.hplims.views.MenuTabSheet;
import no.uib.hplims.views.experiments.ExperimentView;
import no.uib.hplims.views.experiments.ExperimentsTab;
import no.uib.hplims.views.orders.OrderEditor;
import no.uib.hplims.views.orders.OrderTab;
import no.uib.hplims.views.search.SearchResultsView;

public class Controller implements Serializable {

	private static final long serialVersionUID = -5099231362226118326L;

	private BlastTool bt = null;
	
	// Navigation
	private AbstractParentTab currentView = null;
	private MenuTabSheet mainMenu = null;

	// Sequence validation
	private RuleFactory rf = null;
	private ArrayList<String> errorList = null;
	private ArrayList<String> warningList = null;
	private boolean ignoreWarnings = false;
	
	private OrderTab orderTab = null;
	private ExperimentsTab expTab = null;

	// In silico digestion
	private MascotEnzymeReader mascotEnzymeReader = null;

	public Controller() {
	}

	public BlastTool getBlastTool() {
		if (bt == null) {
			bt = new BlastTool();
		}
		
		return bt;
	}
	
	public void startBlastSearch(List<String> validSequences, MyOrder order, String species) {
		
		getBlastTool().startBlastSearch(validSequences, order, species);
		
	}
	
	public void resubmitForBlast(ArrayList<String> sequences, MyOrder order, String species) {
		
		getBlastTool().resubmitForBlast(sequences, order, species);
		
	}
	
	/**
	 * @param order
	 * @return
	 */
	public int findTotalNumberOfSequences(MyOrder order) {
		return getBlastTool().findTotalNumberOfSequences(order);
	}
	
	/**
	 * 
	 * @param order
	 * @return
	 */
	public int findRemainingNumberOfSequences(MyOrder order) {
		return getBlastTool().findRemainingNumberOfSequences(order);
	}

	/**
	 * @return the rf
	 */
	public RuleFactory getRf() {
		if (rf == null) {
			rf = new RuleFactory();
		}
		return rf;
	}

	public ArrayList<Rule> getActiveRules() {
		return getRf().getActiveRules();
	}

	public ArrayList<String> getErrorList() {
		if (errorList == null) {
			errorList = new ArrayList<String>();
		}
		return errorList;
	}

	public ArrayList<String> getWarningList() {
		if (warningList == null) {
			warningList = new ArrayList<String>();
		}
		return warningList;
	}

	public void resetValidationLists() {
		errorList = null;
		warningList = null;
		System.out.println("Error and warning lists reset.");
	}

	public void setIgnoreWarnings(boolean booleanValue) {
		this.ignoreWarnings = booleanValue;
	}

	public boolean getIgnoreWarnings() {
		return this.ignoreWarnings;
	}

	/**
	 * Counts the number of times the given peptide sequence exists in the
	 * database, not including the peptide currently being looked at.
	 * 
	 * @param sequence
	 * @return
	 */
	public int getNumberOfTimesPreviouslyOrdered(String sequence) {

		String query = "p.peptideSequence LIKE :query";
		Map<String, Object> parameters = new HashMap<String, Object>();
		parameters.put("query", sequence);

		int i = FacadeFactory.getFacade()
				.count(Peptide.class, query, parameters).intValue() - 1;

		if (i >= 0) {
			return i;
		} else {
			return 0;
		}
	}

	/**
	 * Finds all peptides related to the query string
	 * 
	 * @param query
	 *            The query to search for
	 */
	public void performQuickSearch(String query) {
		String input = "%" + query + "%";

		String buildQuery = "SELECT p FROM Peptide AS p WHERE p.peptideSequence LIKE :query"
				+ " OR p.uniProtAccession LIKE :query OR p.peptideName LIKE :query";
		Map<String, Object> parameters = new HashMap<String, Object>();
		parameters.put("query", input);

		List<Peptide> list = FacadeFactory.getFacade().list(buildQuery,
				parameters);

		System.out.println("Results size: " + list.size());

		Component resultLayout = buildSearchResultsTab(query, list);

// TODO		getMenuTabSheet().addNewClosableTab(resultLayout);
	}

	/**
	 * Creates a new layout to display the search result for the given query.
	 * 
	 * @param query
	 *            The query
	 * @param results
	 *            The search result given the query
	 * @return A layout component showing the search result in a table
	 */
	private Component buildSearchResultsTab(String query, List<Peptide> results) {

		return new SearchResultsView(query, results);
	}

	

	public ArrayList<String> startInSilicoDigestion(String input,
			String enzymeName) {
		List<String> proteinSequences = null;

		if (input.startsWith(">")) {
			/*
			 * Input is in FASTA form
			 */
			// Split sequences on FASTA character
			String[] strings = input.split(">");
			for (int i = 0; i < strings.length; i++) {
				if (strings[i].length() > 0) {
					// Re-add the FASTA character
					strings[i] = ">" + strings[i];
				}
			}
			// Add all sequences to list
			proteinSequences = new ArrayList<String>();
			proteinSequences.addAll(Arrays.asList(strings));

			System.out.println(proteinSequences.size());

			// Go through list and remove all empty strings
			Iterator<String> it = proteinSequences.iterator();
			while (it.hasNext()) {
				String s = it.next();
				if (s.length() < 1) {
					it.remove();
				}
			}
		} else {
			/*
			 * Input is regular protein sequences, separated by line break.
			 */
			proteinSequences = Arrays.asList(input.split("\n"));
		}
		System.out.println(proteinSequences.size());
		for (String s : proteinSequences) {
			System.out.println(s);
		}
		return inSilicoDigestion(proteinSequences, enzymeName);
	}

	/**
	 * Starts in silico digestion on a list of protein sequences and returns a
	 * list of all the peptides created from this digestion.
	 * 
	 * @param proteinSequences
	 *            List of protein sequences to digest in silico
	 * @param enzymeName
	 *            The enzyme to be used for cleavage
	 * @return List of peptides created from in silico digestion
	 */
	private ArrayList<String> inSilicoDigestion(List<String> proteinSequences,
			String enzymeName) {
		Enzyme selectedEnzyme = getMascotEnzymeReader().getEnzyme(enzymeName);
		selectedEnzyme.setMiscleavages(0);

		ArrayList<String> allPeptides = new ArrayList<String>();

		Protein[] cleavedPeptides;
		for (String proteinSequence : proteinSequences) {

			// Cleave sequences

			if (proteinSequence.startsWith(">")) {

				// for FASTA sequences
				Protein protein = new Protein(proteinSequence);
				cleavedPeptides = selectedEnzyme.cleave(protein);
				// cleanProteinSequence = protein.getSequence().getSequence();
			} else {

				// not FASTA format, assume sequence only, but remove white
				// space and line shifts
				String cleanProteinSequence = proteinSequence;
				cleanProteinSequence = cleanProteinSequence.replaceAll("\\W",
						"");
				cleanProteinSequence = cleanProteinSequence
						.replaceAll("\n", "");
				cleavedPeptides = selectedEnzyme.cleave(new Protein(new String(
						"no header"), cleanProteinSequence));
			}

			// Add cleaved peptides to list of all peptides
			for (int i = 0; i < cleavedPeptides.length; i++) {
				allPeptides.add(cleavedPeptides[i].getSequence().getSequence());
			}
		}

		return allPeptides;
	}

	private MascotEnzymeReader getMascotEnzymeReader() {
		if (mascotEnzymeReader == null) {
			try {
				// locate the file with the enzyme details
				InputStream in = this.getClass().getClassLoader()
						.getResourceAsStream("enzymes.txt");
				if (in != null) {
					// get enzyme details from file
					mascotEnzymeReader = new MascotEnzymeReader(in);
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return mascotEnzymeReader;
	}

	public List<String> getEnzymeNames() {
		// Get enzyme names
		String[] allEnzymeNames = getMascotEnzymeReader().getEnzymeNames();
		// Order the enzymes
		Arrays.sort(allEnzymeNames);
		// Return as a list of enzyme names
		return Arrays.asList(allEnzymeNames);
	}

	public String lookUpUniProtAccessions(String input) {
		List<String> result = UniProtQueryTool
				.findSequencesByUniProtAccessionNumbers(Arrays.asList(input
						.split("\n")));
		String sequences = "";
		for (String s : result) {
			sequences += s + "\n";
		}
		return sequences;
	}
	
	
	/*
	 * Orders: All methods and variables utilized for orders
	 */
	private ArrayList<MyOrder> orderList = null;
	private BeanItemContainer<MyOrder> orderContainer = null;
	
	public BeanItemContainer<MyOrder> getOrderContainer() {
		if (orderContainer == null) {
			orderContainer = new BeanItemContainer<MyOrder>(MyOrder.class);
			
			// Get all orders from DB and add to container
			
			orderContainer.addAll(getAllOrders());
			orderContainer.sort(new Object[] { "id" }, new boolean[] { false });
		}
		return orderContainer;
	}
	
	public ArrayList<MyOrder> getAllOrders() {
		if (orderList == null) {
			orderList = new ArrayList<MyOrder>();
			List<MyOrder> list = FacadeFactory.getFacade().list(MyOrder.class);
			orderList.addAll(list);
		}
		return orderList;
	}
	
	public void refreshOrderContainer() {
		boolean removed = orderContainer.removeAllItems();
		if (removed) {
			orderContainer.addAll(this.getAllOrders());
			orderContainer.sort(new Object[] { "id" },
					new boolean[] { false });
		}
	}
	
	/**
	 * Adds a new tab to MenuTabSheet to display an order. If the order
	 * parameter is null, a new, empty order is created and displayed.
	 * 
	 * @param order
	 *            The order to display, if null a new order is created
	 */
	public void showOrder(MyOrder order) {
		MyOrder newOrder = null;
		if (order == null) {
			newOrder = createNewOrder();
		} else {
			newOrder = order;
		}
		// Create new order editor
		OrderEditor oe = new OrderEditor(newOrder);
		// Add the new order editor to order tab
		getOrderTab().addNewClosableTab(oe);
		// Activate order tab
		getMainMenu().activate(getOrderTab());
	}

	/**
	 * Creates a new MyOrder object
	 * 
	 * @return The new MyOrder
	 */
	public MyOrder createNewOrder() {
		MyOrder newOrder = new MyOrder();
		User loggedInUser = (User) SessionHandler.get();
		Date date = new Date();
		newOrder.setCreatedOn(date);
		newOrder.setOrderTitle("Order " + newOrder.getId());
		newOrder.setOwner(loggedInUser);
		FacadeFactory.getFacade().store(newOrder);
		newOrder.setOrderTitle("Order " + newOrder.getId());
		FacadeFactory.getFacade().store(newOrder);

		this.getAllOrders().add(newOrder);
		this.refreshOrderContainer();

		return newOrder;
	}

	/**
	 * Deletes an order from DB. Updates list of orders.
	 * 
	 * @param e
	 *            The Experiment to delete
	 */
	public void deleteOrder(MyOrder e) {
		if (getAllOrders().contains(e)) {
			getAllOrders().remove(e);
		}
		FacadeFactory.getFacade().delete(e);
		refreshOrderContainer();
	}

	public void updateOrder(MyOrder e) {
		if (!getAllOrders().contains(e)) {
			getAllOrders().add(e);
		} else {
			getAllOrders().remove(e);
			getAllOrders().add(e);
		}
		FacadeFactory.getFacade().store(e);
		refreshOrderContainer();
	}

	
	/*
	 * Experiments: All methods and variables utilized for experiments
	 */
	private Experiment currentExperiment = null;
	private ArrayList<Experiment> experimentList = null;
	private BeanItemContainer<Experiment> experimentContainer = null;

	/**
	 * @return
	 */
	public BeanItemContainer<Experiment> getExperimentContainer() {
		if (experimentContainer == null) {
			experimentContainer = new BeanItemContainer<Experiment>(
					Experiment.class);
			
			// Get all experiments from DB and add to container
			List<Experiment> experiments = this.getAllExperiments();
			experimentContainer.addAll(experiments);
			experimentContainer
					.sort(new Object[] { "id" }, new boolean[] { false });
		}

		return experimentContainer;
	}

	public void refreshExperimentContainer() {
		boolean removed = experimentContainer.removeAllItems();
		if (removed) {
			// List<Experiment> list =
			// FacadeFactory.getFacade().list(Experiment.class);
			experimentContainer.addAll(this.getAllExperiments());
			experimentContainer.sort(new Object[] { "id" },
					new boolean[] { false });
		}
	}

	/**
	 * Adds a new tab to MenuTabSheet to display an order. If the order
	 * parameter is null, a new, empty order is created and displayed.
	 * 
	 * @param exp
	 *            The experiment to display, if null a new order is created
	 */
	public void showExperiment(Experiment exp) {
		Experiment newExp = null;
		if (exp == null) {
			newExp = createNewExperiment();
		} else {
			newExp = exp;
		}
		// Show experiment
		getExpTab().addNewClosableTab(new ExperimentView(newExp));
	}

	/**
	 * Creates a new Experiment object
	 * 
	 * @return The new Experiment
	 */
	public Experiment createNewExperiment() {
		Experiment newExp = new Experiment();
		FacadeFactory.getFacade().store(newExp);
		newExp.setName("Experiment " + newExp.getId());
		newExp.setOwner((User) SessionHandler.get());
		FacadeFactory.getFacade().store(newExp);

		this.getAllExperiments().add(newExp);
		this.setCurrentExperiment(newExp);
		this.refreshExperimentContainer();

		return newExp;
	}

	/**
	 * Deletes an Experiment from DB. Updates list of experiments. Resets
	 * current experiment.
	 * 
	 * @param e
	 *            The Experiment to delete
	 */
	public void deleteExperiment(Experiment e) {
		if (getAllExperiments().contains(e)) {
			getAllExperiments().remove(e);
		}
		if (getCurrentExperiment() == e) {
			currentExperiment = null;
		}
		FacadeFactory.getFacade().delete(e);
		this.refreshExperimentContainer();
	}

	public Experiment getCurrentExperiment() {
		return currentExperiment;
	}

	public void setCurrentExperiment(Experiment e) {
		this.currentExperiment = e;
	}

	public ArrayList<Experiment> getAllExperiments() {
		if (experimentList == null) {
			experimentList = new ArrayList<Experiment>();
			List<Experiment> list = FacadeFactory.getFacade().list(
					Experiment.class);
			experimentList.addAll(list);
		}
		return experimentList;
	}

	public void updateExperiment(Experiment e) {
		if (!experimentList.contains(e)) {
			experimentList.add(e);
		} else {
			experimentList.remove(e);
			experimentList.add(e);
		}
		FacadeFactory.getFacade().store(e);
		refreshExperimentContainer();
	}
	
	private OrderTab getOrderTab() {
		return this.orderTab;
	}

	public void setOrderTab(OrderTab orderTab) {
		this.orderTab = orderTab;
	}

	public ExperimentsTab getExpTab() {
		return expTab;
	}

	public void setExpTab(ExperimentsTab expTab) {
		this.expTab = expTab;
	}
	
	// NAVIGATION

	public AbstractParentTab getCurrentView() {
		return currentView;
	}

	public void setCurrentView(AbstractParentTab currentView) {
		this.currentView = currentView;
	}

	public MenuTabSheet getMainMenu() {
		return mainMenu;
	}

	public void setMainMenu(MenuTabSheet mainMenu) {
		this.mainMenu = mainMenu;
	}
}
